/*
-----------------------------------------------------------
Copyright ?2003 by cTh
Use as you see fit.
Questions : gavocanov@rambler.ru
-----------------------------------------------------------
*/
/***********************************************************************/
#include "Pch.h"
/***********************************************************************/
#include "OgreExportPlugin.h"
#include "Exporter.h"
#include "shaders.h"	// extract shader parameters
/***********************************************************************/
namespace OgreMaxExport
{
	/***********************************************************************/
	using namespace types;
	using namespace utils;
	using namespace TiXML;
	/***********************************************************************/
	maxExporter::maxExporter(void)
	{
	}
	/***********************************************************************/
	maxExporter::~maxExporter(void)
	{
	}
	/***********************************************************************/
	bool maxExporter::setFileNames()
	{
		if (!getOpenFileName(mpIGame->GetSceneFileName(), mFileName))
			return false;

		utils::extractPath(mFileName, mFilePath);
		mFilePath += "\\";
		return true;
	}
	/***********************************************************************/
	void maxExporter::clearStacks()
	{
		for (std::vector<sMeshInfo*>::iterator it = mMeshes.begin(); it != mMeshes.end(); ++it)
		{
			(*it)->pMesh.setNull();
			delete (*it);
		}
		for (std::vector<sObjInfo*>::iterator it = mTargets.begin(); it != mTargets.end(); ++it)
			delete (*it);
		for (std::vector<sObjInfo*>::iterator it = mGrpOwners.begin(); it != mGrpOwners.end(); ++it)
			delete (*it);
		for (std::vector<sBoneInfo*>::iterator it = mBones.begin(); it != mBones.end(); ++it)
			delete (*it);
		for (std::vector<sLightInfo*>::iterator it = mLights.begin(); it != mLights.end(); ++it)
			delete (*it);
		for (std::vector<sCamInfo*>::iterator it = mCameras.begin(); it != mCameras.end(); ++it)
			delete (*it);

		mMeshes.clear();
		mTargets.clear();
		mGrpOwners.clear();
		mLights.clear();
		mCameras.clear();
		mBones.clear();
	}
	/***********************************************************************/
	void maxExporter::initMembers()
	{
		mpCfg = NULL;
		mpIGame = NULL;
		mpLogMgr = NULL;
		mpMatMgr = NULL;
		mpSkelMgr = NULL;
		mpBuffMgr = NULL;
		mpMath = NULL;
		mpMatSer = NULL;
		mpMeshSer = NULL;
		mpSkelSer = NULL;
		mpRGMgr = NULL;
		mpMeshMgr = NULL;
		mMatFileName = "";
		mFileName = "";
		mFilePath = "";
		mDoSelected = false;
		clearStacks();
	}
	/***********************************************************************/
	void maxExporter::initLib()
	{
		initMembers();
		try
		{
			mpLogMgr	= new Ogre::LogManager();
			if (mpLogMgr)
				mpLogMgr->createLog("OgreExporter.log", true, true);
			mpRGMgr = new Ogre::ResourceGroupManager();
			mpMath		= new Ogre::Math();
			mpMeshMgr	= new Ogre::MeshManager();
			mpMeshSer	= new Ogre::MeshSerializer();
			mpBuffMgr	= new Ogre::DefaultHardwareBufferManager();
			mpMatMgr	= new Ogre::MaterialManager();
			mpMatSer	= new Ogre::MaterialSerializer();
			if (mpMatMgr)
				mpMatMgr->initialise();
			mpSkelMgr	= new Ogre::SkeletonManager();
			mpSkelSer	= new Ogre::SkeletonSerializer();
		}
		catch (Ogre::Exception ex)
		{
			Ogre::String strMsg = ex.getFullDescription();
			strMsg = "!!! Something horrible happended, do not try to export anything !!!\nDESCRIPTION\n" + strMsg;
			MessageBox(NULL, strMsg.c_str(), "some Ogre shit happens :(", MB_OK | MB_ICONERROR);
		}
	}
	/***********************************************************************/
	void maxExporter::shutDownLib()
	{
		if (mpMatSer)
			delete mpMatSer;
		if (mpMeshSer)
			delete mpMeshSer;
		if (mpSkelSer)
			delete mpSkelSer;

		if (mpMeshMgr)
			delete mpMeshMgr;
		if (mpSkelMgr)
			delete mpSkelMgr;
		if (mpBuffMgr)
			delete mpBuffMgr;
		if (mpMatMgr)
			delete mpMatMgr;

		if (mpRGMgr)
			delete mpRGMgr;

		if (mpMath)
			delete mpMath;
		if (mpLogMgr)
			delete mpLogMgr;
		if (mpCfg)
			delete mpCfg;

		initMembers();
	}
	/***********************************************************************/
	bool maxExporter::initIGame(bool doSelected)
	{
		initLib();
		mpIGame = GetIGameInterface();
		mDoSelected = doSelected;
		IGameConversionManager *pCM = GetConversionManager();
		pCM->SetCoordSystem(IGameConversionManager::CoordSystem::IGAME_OGL);
		mpIGame->SetPropertyFile("IGameProp.xml");
		mpIGame->InitialiseIGame(mDoSelected);
		if (mpIGame->GetTopLevelNodeCount() == 0)
			return false;

		return true;
	}
	/***********************************************************************/
	void maxExporter::shutDownIGame()
	{
		if (mpIGame)
		{
			mpIGame->ReleaseIGame();
			mpIGame = NULL;
		}
		shutDownLib();
	}
	/***********************************************************************/
	void maxExporter::doMaterial(IGameMaterial *pIMat)
	{
		if (!pIMat)
			return;
		// check if we'll have to make a blank material
		Ogre::String matName = checkName(pIMat->GetMaterialName());
		//
		showProgress("Exporting material : " + Ogre::String(matName));
		//
		Point3 color;
		Ogre::MaterialPtr pMat;// = NULL;
		// may be duplicate so "try"
		try							
		{
			if (mpMatMgr->getByName(matName) == Ogre::ResourcePtr())
				pMat = mpMatMgr->create(matName, Ogre::String("3dsMaxExport"));
			else
				pMat.setNull();
		}
		catch (Ogre::Exception ex)	
		{
			pMat.setNull();
		}
		// exit if material already exist...
		if (pMat == Ogre::ResourcePtr())
			return;
		// start with no texture, will find it  later
		bool hasTex = false;
		// get/create technique
		Ogre::Technique *pTec = pMat->getBestTechnique();
		if (!pTec)
		{
			pTec = pMat->getTechnique(0);
			if (!pTec)
				pTec = pMat->createTechnique();
		}
		// do pass
		Ogre::Pass *pPass = pTec->getPass(0);
		if (!pPass)
			pPass = pTec->createPass();
		// do texture layers if any
		int numTex = 0;
		int subMatCount = pIMat->GetSubMaterialCount();
		bool doThis = false;
		if (subMatCount == 0 && pIMat->IsEntitySupported())
		{
			subMatCount = 1;
			doThis = true;
		}
		for (int n = 0; n < subMatCount; ++n)
		{
			IGameMaterial *pSMat = NULL;
			if (doThis)
				pSMat = pIMat;
			else
				pSMat = pIMat->GetSubMaterial(n);

			if (pSMat->IsEntitySupported())
				numTex = pSMat->GetNumberOfTextureMaps();
			else
				numTex = 0;

			// retrieve max material for OgreShader export
			Mtl* pMtl = pSMat->GetMaxMaterial();
			if(pMtl->ClassID() == Class_ID(2,0)) // if it's standard material, we have lots parameters to export :)
			{
				int shader_basic_paramblock_id = 0;
				enum {
					shaderbasic_id = 0,
					shaderbasic_wire,
					shaderbasic_twosided,	// important: if its value = 1, we add 'cull_hardware none' in this pass
					shaderbasic_face,
					shaderbasic_faceted, 
					shaderbasic_name,
				};
				int shader_id = 0;
				int shader_wire = 0;
				int shader_twosided = 0;
				int shader_face = 0;
				int shader_faceted = 0;
				TSTR shader_name = "";	// record this shader name, if using 'Ogre shader', there must be more parameters for Ogre.

				IParamBlock2 *pShaderBasicBlock = NULL;
				pShaderBasicBlock = pMtl->GetParamBlock(shader_basic_paramblock_id);
				if(pShaderBasicBlock != NULL)
				{
					shader_id = pShaderBasicBlock->GetInt(shaderbasic_id);
					shader_wire = pShaderBasicBlock->GetInt(shaderbasic_wire);
					shader_twosided = pShaderBasicBlock->GetInt(shaderbasic_twosided);
					shader_face = pShaderBasicBlock->GetInt(shaderbasic_face);
					shader_faceted = pShaderBasicBlock->GetInt(shaderbasic_faceted);
					shader_name = pShaderBasicBlock->GetStr(shaderbasic_name);

					// important: if shaderbasic_twosided is ON, we add 'cull_hardware none' in this pass
					if(shader_twosided) pPass->setCullingMode(Ogre::CULL_NONE);
				}

				// cast this pointer to StdMat2, assuming it's always StdMat2
				StdMat2 *pStdMat2 = (StdMat2 *)pMtl;
				float opacity = pStdMat2->GetOpacity(0);
				Color diffuse = pStdMat2->GetDiffuse(0);
				pPass->setDiffuse(diffuse.r, diffuse.g, diffuse.b, opacity);
				Color ambient = pStdMat2->GetAmbient(0);
				pPass->setAmbient(ambient.r, ambient.g, ambient.b);
				Color specular = pStdMat2->GetSpecular(0);
				pPass->setSpecular(specular.r, specular.g, specular.b, 1.0f);
				pPass->setShininess(pStdMat2->GetShininess(0) * 128);
				Color emissive;
				if ( pStdMat2->GetSelfIllumColorOn(0) )
					emissive = pStdMat2->GetSelfIllumColor(0);
				else
					emissive = Color(pStdMat2->GetSelfIllum(0), pStdMat2->GetSelfIllum(0), pStdMat2->GetSelfIllum(0));
				pPass->setSelfIllumination(emissive.r, emissive.g, emissive.b);

				// if using ogre shader, we can export more ogre material parameters.
				int ogreshader_paramblock_id = 0;
				enum {
					ogre_applylighting = 0,	// TYPE_BOOL
					ogre_applyfog,			// TYPE_BOOL
					ogre_ambient,			// TYPE_RGBA - acquire this param by StdMat2
					ogre_diffuse,			// TYPE_RGBA - acquire this param by StdMat2
					ogre_specular,			// TYPE_RGBA - acquire this param by StdMat2
					ogre_specularlevel,		// TYPE_FLOAT
					ogre_use_self_illum_color,	// TYPE_BOOL - acquire this param by StdMat2
					ogre_self_illum_color,	// TYPE_RGBA - acquire this param by StdMat2
					ogre_self_illum_amnt,	// TYPE_FLOAT - acquire this param by StdMat2
					ogre_glossiness,		// TYPE_INT
					ogre_sceneblend,		// TYPE_RADIOBTN_INDEX
					ogre_sourceblend,		// TYPE_INDEX
					ogre_destblend,			// TYPE_INDEX
					ogre_alphatest,			// TYPE_BOOL
					ogre_alphatest_ref,		// TYPE_INT
					ogre_alphatest_func,	// TYPE_INDEX
					ogre_writecolor,		// TYPE_BOOL
					ogre_writedepth,		// TYPE_BOOL
					ogre_checkdepth,		// TYPE_BOOL
					ogre_checkdepth_func,	// TYPE_INDEX
					ogre_depthbias,			// TYPE_FLOAT
				};
				int ogreshader_applylighting = 0;
				int ogreshader_applyfog = 0;	// currently not used.
				Color ogreshader_ambient;
				Color ogreshader_diffuse;
				Color ogreshader_specular;
				float ogreshader_specularlevel;
				int ogreshader_use_self_illum_color = 0;
				Color ogreshader_self_illum_color;
				float ogreshader_self_illum_amnt = 0;
				float ogreshader_glossiness = 0;
				int ogreshader_sceneblend = 0;
				int ogreshader_sourceblend = 0;
				int ogreshader_destblend = 0;
				int ogreshader_alphatest = 0;
				int ogreshader_alphatest_ref = 0;
				int ogreshader_alphatest_func = 0;
				int ogreshader_writecolor = 0;
				int ogreshader_writedepth = 0;
				int ogreshader_checkdepth = 0;
				int ogreshader_checkdepth_func = 0;
				int ogreshader_depthbias = 0;

				if(shader_name == TSTR("Ogre shader"))
				{
					Shader *pOgreShader = pStdMat2->GetShader();
					IParamBlock2 *pOgreShaderBlock = pOgreShader->GetParamBlock(ogreshader_paramblock_id);
					ogreshader_applylighting = pOgreShaderBlock->GetInt(ogre_applylighting);
					ogreshader_applyfog = pOgreShaderBlock->GetInt(ogre_applyfog);
					ogreshader_ambient = pOgreShaderBlock->GetColor(ogre_ambient);
					ogreshader_diffuse = pOgreShaderBlock->GetColor(ogre_diffuse);
					ogreshader_specular = pOgreShaderBlock->GetColor(ogre_specular);
					ogreshader_specularlevel = pOgreShaderBlock->GetFloat(ogre_specularlevel);
					ogreshader_use_self_illum_color = pOgreShaderBlock->GetInt(ogre_use_self_illum_color);
					ogreshader_self_illum_color = pOgreShaderBlock->GetColor(ogre_self_illum_color);
					ogreshader_self_illum_amnt = pOgreShaderBlock->GetFloat(ogre_self_illum_amnt);
					ogreshader_glossiness = pOgreShaderBlock->GetFloat(ogre_glossiness);
					ogreshader_sceneblend = pOgreShaderBlock->GetInt(ogre_sceneblend);
					ogreshader_sourceblend = pOgreShaderBlock->GetInt(ogre_sourceblend);
					ogreshader_destblend = pOgreShaderBlock->GetInt(ogre_destblend);
					ogreshader_alphatest = pOgreShaderBlock->GetInt(ogre_alphatest);
					ogreshader_alphatest_ref = pOgreShaderBlock->GetInt(ogre_alphatest_ref);
					ogreshader_alphatest_func = pOgreShaderBlock->GetInt(ogre_alphatest_func);
					ogreshader_writecolor = pOgreShaderBlock->GetInt(ogre_writecolor);
					ogreshader_writedepth = pOgreShaderBlock->GetInt(ogre_writedepth);
					ogreshader_checkdepth = pOgreShaderBlock->GetInt(ogre_checkdepth);
					ogreshader_checkdepth_func = pOgreShaderBlock->GetInt(ogre_checkdepth_func);
					ogreshader_depthbias = pOgreShaderBlock->GetInt(ogre_depthbias);

					pPass->setLightingEnabled( ogreshader_applylighting==1?true:false);
					//if(ogreshader_use_self_illum_color)
					//	pPass->setSelfIllumination(Ogre::ColourValue(ogreshader_self_illum_color.r, ogreshader_self_illum_color.g, ogreshader_self_illum_color.b, 1.0f));
					//else
					//	pPass->setSelfIllumination(Ogre::ColourValue(ogreshader_self_illum_amnt, ogreshader_self_illum_amnt, ogreshader_self_illum_amnt, 1.0f));
					switch(ogreshader_sceneblend)
					{
					case 0:	// opaque
						pPass->setSceneBlending(Ogre::SBT_REPLACE);
						break;
					case 1: // Add
						pPass->setSceneBlending(Ogre::SBT_ADD);
						break;
					case 2: // Modulate
						pPass->setSceneBlending(Ogre::SBT_MODULATE);
						break;
					case 3: // Colour
						pPass->setSceneBlending(Ogre::SBT_TRANSPARENT_COLOUR);
						break;
					case 4: // Alpha Blend
						pPass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
						break;
					case 5: // Custom blending mode
						Ogre::SceneBlendFactor src_factor;
						switch(ogreshader_sourceblend)
						{
						case 0: src_factor = Ogre::SBF_ONE; break;
						case 1: src_factor = Ogre::SBF_ZERO; break;
						case 2: src_factor = Ogre::SBF_DEST_COLOUR; break;
						case 3: src_factor = Ogre::SBF_SOURCE_COLOUR; break;
						case 4: src_factor = Ogre::SBF_ONE_MINUS_DEST_COLOUR; break;
						case 5: src_factor = Ogre::SBF_ONE_MINUS_SOURCE_COLOUR; break;
						case 6: src_factor = Ogre::SBF_DEST_ALPHA; break;
						case 7: src_factor = Ogre::SBF_SOURCE_ALPHA; break;
						case 8: src_factor = Ogre::SBF_ONE_MINUS_DEST_ALPHA; break;
						case 9: src_factor = Ogre::SBF_ONE_MINUS_SOURCE_ALPHA; break;
						}
						Ogre::SceneBlendFactor dest_factor;
						switch(ogreshader_destblend)
						{
						case 0: dest_factor = Ogre::SBF_ONE; break;
						case 1: dest_factor = Ogre::SBF_ZERO; break;
						case 2: dest_factor = Ogre::SBF_DEST_COLOUR; break;
						case 3: dest_factor = Ogre::SBF_SOURCE_COLOUR; break;
						case 4: dest_factor = Ogre::SBF_ONE_MINUS_DEST_COLOUR; break;
						case 5: dest_factor = Ogre::SBF_ONE_MINUS_SOURCE_COLOUR; break;
						case 6: dest_factor = Ogre::SBF_DEST_ALPHA; break;
						case 7: dest_factor = Ogre::SBF_SOURCE_ALPHA; break;
						case 8: dest_factor = Ogre::SBF_ONE_MINUS_DEST_ALPHA; break;
						case 9: dest_factor = Ogre::SBF_ONE_MINUS_SOURCE_ALPHA; break;
						}
						pPass->setSceneBlending(src_factor, dest_factor);
						break;
					}
					if(ogreshader_alphatest)
					{
						Ogre::CompareFunction alphatest_func;
						switch(ogreshader_alphatest_func)
						{
						case 0: // always fail
							alphatest_func = Ogre::CMPF_ALWAYS_FAIL; break;
						case 1: // always pass
							alphatest_func = Ogre::CMPF_ALWAYS_PASS; break;
						case 2: // less
							alphatest_func = Ogre::CMPF_LESS; break;
						case 3: // less or equal
							alphatest_func = Ogre::CMPF_LESS_EQUAL; break;
						case 4: // equal
							alphatest_func = Ogre::CMPF_EQUAL; break;
						case 5: // not equal
							alphatest_func = Ogre::CMPF_NOT_EQUAL; break;
						case 6: // greater or equal
							alphatest_func = Ogre::CMPF_GREATER_EQUAL; break;
						case 7: // greater
							alphatest_func = Ogre::CMPF_GREATER; break;
						}
						pPass->setAlphaRejectSettings(alphatest_func, ogreshader_alphatest_ref);
					}
					pPass->setColourWriteEnabled((ogreshader_writecolor == 1)?true:false);
					pPass->setDepthWriteEnabled((ogreshader_writedepth == 1)?true:false);
					pPass->setDepthCheckEnabled((ogreshader_checkdepth == 1)?true:false);
					Ogre::CompareFunction depthcheck_func;
					switch(ogreshader_checkdepth_func)
					{
					case 0: // always fail
						depthcheck_func = Ogre::CMPF_ALWAYS_FAIL; break;
					case 1: // always pass
						depthcheck_func = Ogre::CMPF_ALWAYS_PASS; break;
					case 2: // less
						depthcheck_func = Ogre::CMPF_LESS; break;
					case 3: // less or equal
						depthcheck_func = Ogre::CMPF_LESS_EQUAL; break;
					case 4: // equal
						depthcheck_func = Ogre::CMPF_EQUAL; break;
					case 5: // not equal
						depthcheck_func = Ogre::CMPF_NOT_EQUAL; break;
					case 6: // greater or equal
						depthcheck_func = Ogre::CMPF_GREATER_EQUAL; break;
					case 7: // greater
						depthcheck_func = Ogre::CMPF_GREATER; break;
					}
					pPass->setDepthFunction(depthcheck_func);
					pPass->setDepthBias(ogreshader_depthbias);

				}
			}

			for (int m = 0; m < numTex; ++m)
			{
				IGameTextureMap *pITex = pSMat->GetIGameTextureMap(m);
				if (pITex)
				{
					if (pITex->IsEntitySupported())
					{
						Ogre::String texName = (char *)pITex->GetBitmapFileName();
						std::vector<Ogre::String> tstr = Ogre::StringUtil::split(texName, "\\");
						if (tstr.begin() != tstr.end())
							texName = tstr.back();
						//
						Ogre::TextureUnitState *pTU = NULL;
						if (!mpCfg->mMultiTex)
						{
							if (pITex->GetStdMapSlot() == ID_DI)
							{
								pTU = pPass->createTextureUnitState(texName);
								pTU->setTextureCoordSet(n);
								// TO-DO: Add Texture transform here. i'll do it next time.
								// Enable anisotropic filtering
								pTU->setTextureFiltering(Ogre::TFO_ANISOTROPIC);
								pTU->setTextureAnisotropy(8);
								hasTex = true;
							}
						}
						else
						{
							pTU = pPass->createTextureUnitState(texName);
							pTU->setTextureCoordSet(n);
							// TO-DO: Add Texture Transform here. i'll do it next time.
							// Enable anisotropic filtering
							pTU->setTextureFiltering(Ogre::TFO_ANISOTROPIC);
							pTU->setTextureAnisotropy(8);
							hasTex = true;
						}
					}
				}// if texture
				if (!mpCfg->mMultiTex && hasTex)
					break;
			}// for numTex
			// export colors if no texture present
			if (!hasTex)
			{
				if (pSMat->GetAmbientData())
				{
					color = readPoint3Prop(pSMat->GetAmbientData());
					pMat->setAmbient(color.x, color.y, color.z);
				}
				if (pSMat->GetDiffuseData())
				{
					color = readPoint3Prop(pSMat->GetDiffuseData());
					pMat->setDiffuse(color.x, color.y, color.z, 0);
				}
				if (pSMat->GetSpecularData())
				{
					color = readPoint3Prop(pSMat->GetSpecularData());
					pMat->setSpecular(color.x, color.y, color.z, 0);
				}
			}// !tex
		}// for subMaterials
		// queue it for export
		mpMatSer->queueForExport(pMat);
		pMat.setNull();
	}
	/***********************************************************************/
	void maxExporter::doMesh(IGameNode *pNode)
	{
		IGameMesh *pIMesh = (IGameMesh *)pNode->GetIGameObject();
		if (!pIMesh->IsEntitySupported())
			return;

		if (pIMesh->GetNumberOfVerts() <= 0)
			return;
		// init data 
		if (mpCfg->mNormals)
		{
			pIMesh->SetCreateOptimizedNormalList();
			if (mpCfg->mWeightNormals)
				pIMesh->SetUseWeightedNormals();
		}
		if (!pIMesh->InitializeData())
			return;

		Ogre::MeshPtr pMesh = mpMeshMgr->createManual(checkName(pNode->GetName()), Ogre::String("3dsMaxExport"));
		Ogre::SubMesh *pSubMesh	= NULL;
		Ogre::VertexData *pData	= NULL;
		Ogre::VertexBufferBinding *pBind = NULL;
		Ogre::VertexDeclaration *pDecl = NULL;
		Ogre::HardwareVertexBufferSharedPtr pBuf;// = NULL;
		Ogre::Real *pReal= NULL;
		Ogre::Vector3 min, max, currPos;
		Ogre::Real maxSquaredRadius;
		bool firstVert = true;
		// Get the transformation matrix
		GMatrix worldTransform = pIMesh->GetIGameObjectTM();
		Ogre::Matrix4 world2obj = convert(worldTransform);
		world2obj.inverse();
		Ogre::Matrix4 world2objN = world2obj;
		world2objN[3][0] = 0;
		world2objN[3][1] = 0;
		world2objN[3][2] = 0;
		// signal if smoothing groups are not present to fall back to material ID's
		bool smFailed = false;
		if (mpCfg->mSmtGrp)
		{
			// create subMeshes from Smoothing groups
			Tab<DWORD> smGrps = pIMesh->GetActiveSmgrps();
			for (int x = 0; (x < smGrps.Count()) && (smFailed != true); x++)
			{
				// update progress
				showProgress("MESH '" + pMesh->getName() + "' : SubMeshes/BySmoothingGroups ", x, smGrps.Count());
				// go
				Tab<FaceEx*> smFaces = pIMesh->GetFacesFromSmgrp(smGrps[x]);
				int addr = 0;
				smFaces.Addr(addr);
				if (addr) // check if faces exists for this smGroup, sometimes this return 0
				{
					Tab<int> tiFaceIdx = pIMesh->GetFaceIndexFromSmgrp(smGrps[x]);
					// get/create material
					Ogre::String matName = pMesh->getName() + "_SubMesh_" + Ogre::StringConverter::toString(x);
					IGameMaterial *pMat = pIMesh->GetMaterialFromFace(smFaces[0]);
					if (pMat)
						matName = checkName(pMat->GetMaterialName());
					if (mpCfg->mMaterials)
						doMaterial(pIMesh->GetMaterialFromFace(smFaces[0]));
					// create subMesh
					pSubMesh = pMesh->createSubMesh(matName);
					pSubMesh->setMaterialName(matName);
					pSubMesh->useSharedVertices = false;
					pSubMesh->indexData = new Ogre::IndexData();
					pSubMesh->vertexData = new Ogre::VertexData();
					size_t numVert = pSubMesh->vertexData->vertexCount = smFaces.Count() * 3;
					pDecl = pSubMesh->vertexData->vertexDeclaration;
					pBind = pSubMesh->vertexData->vertexBufferBinding;

					unsigned short iBinding = 0;
					// do positions first
					pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
					pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
					pBind->setBinding(iBinding, pBuf);
					pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
					#ifndef maximum
#define maximum(a,b)            (((a) > (b)) ? (a) : (b))
#endif

					for (int n = 0; n < smFaces.Count(); n++)
					{
						FaceEx *pFace = smFaces[n];
						for (int i = 0; i < 3; i++)
						{
							Point3 v; 
							if(pIMesh->GetVertex(pFace->vert[i], v))
							{
								*pReal++ = v.x;
								*pReal++ = v.y;
								*pReal++ = v.z;
								// Deal with bounds
								currPos = Ogre::Vector3(v.x, v.y, v.z);
								if (firstVert)
								{
									min = max = currPos;
									maxSquaredRadius = currPos.squaredLength();
									firstVert = false;
								}
								else
								{
									min.makeFloor(currPos);
									max.makeCeil(currPos);
									maxSquaredRadius = maximum(maxSquaredRadius, currPos.squaredLength());
								}
							}
						}// for vertex
					}// for faces
					// do normals
					if (mpCfg->mNormals && pIMesh->GetNumberOfNormals())
					{
						iBinding++;
						pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
						pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
						pBind->setBinding(iBinding, pBuf);
						pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
						for (int n = 0; n < smFaces.Count(); n++)
						{
							FaceEx *pFace = smFaces[n];
							for (int i = 0; i < 3; i++)
							{
								Point3 maxNor; 
								if(pIMesh->GetNormal(pFace->norm[i], maxNor))
								{
									Ogre::Vector3 nor;
									nor.x = maxNor.x;
									nor.y = maxNor.y;
									nor.z = maxNor.z;
									nor =  world2objN * nor;
									*pReal++ = nor.x;
									*pReal++ = nor.y;
									*pReal++ = nor.z;
								}
							}// for vertex
						}//for face
					}// normals
					// do tex.coords
					if (mpCfg->mTCoords)
					{
						int numTC = 0;
						if (pIMesh->GetNumberOfTexVerts())
						{
							iBinding++;
							pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, numTC);
							pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
							pBind->setBinding(iBinding, pBuf);
							pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
							for (int n = 0; n < smFaces.Count(); n++)
							{
								FaceEx *pFace = smFaces[n];
								for (int i = 0; i < 3; i++)
								{
									Point2 tc; 
									if(pIMesh->GetTexVertex(pFace->texCoord[i], tc))
									{
										*pReal++ = tc.x;
										*pReal++ = (float)(1.0f - tc.y);
									}
								}// for vertex
							}//for face
						}
						// do multi UV's if asked
						Tab<int> xTab = pIMesh->GetActiveMapChannelNum();
						int iNumMapChannels = xTab.Count();
						if (iNumMapChannels && (mpCfg->mUV2 || mpCfg->mUVAll))
						{
							for (int iMapChannel = 2; iMapChannel < iNumMapChannels + 2; iMapChannel++)
							{
								// first check if we'll get something
								DWORD aiFaceIndex[3];
								if (pIMesh->GetMapFaceIndex(iMapChannel, tiFaceIdx[0], (DWORD*)&aiFaceIndex))
								{
									iBinding++;
									numTC++;
									pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, numTC);
									pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
									pBind->setBinding(iBinding, pBuf );
									pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
									for (int n = 0; n < smFaces.Count(); n++)
									{
										FaceEx *pFace = smFaces[n];
										pIMesh->GetMapFaceIndex(iMapChannel, tiFaceIdx[n], (DWORD*)&aiFaceIndex );
										for (int i = 0; i < 3; i++)
										{
											Point3 tc; 
											if (pIMesh->GetMapVertex(iMapChannel, aiFaceIndex[i], tc))
											{
												*pReal++ = tc.x;
												*pReal++ = (float)(1.0f - tc.y);
											} 
											else
											{
												*pReal++ = 0;
												*pReal++ = 0;
											}
										}// for vertex
									}//for face
									// we got the first mapping channel so check if others are requested
									if (!mpCfg->mUVAll)
										break;
								}// if GetMapFaceIndex
							} //iMapChannels
						}// multi tex sets
					}// text coords
					// do vertex colors
					if (mpCfg->mColors && pIMesh->GetNumberOfColorVerts())
					{
						iBinding++;
						pDecl->addElement(iBinding, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
						pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
						pBind->setBinding(iBinding, pBuf);
						unsigned long *pColor = static_cast<unsigned long *>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
						for (int n = 0; n < smFaces.Count(); n++)
						{
							FaceEx *pFace = smFaces[n];
							for (int i = 0; i < 3; i++)
							{
								Point3 c; 
								if(pIMesh->GetColorVertex(pFace->color[i], c))
								{
									Ogre::ColourValue cv = Ogre::ColourValue(c.x, c.y, c.z, 1);
									*pColor++ = cv.getAsABGR();
								}
							}// for vertex
						}// for faces
					}// vertex colors
				}// if faces exist
				else // say the faces are not present in the log at least
				{
					mpLogMgr->getSingletonPtr()->logMessage("MESH '" + pMesh->getName() + "' have fucked'up face list if using smoothing groups, will export based on material ID's instead...");
					smFailed = true;
				}
			}// for smoothing group
		}// use smoothing groups
		if (!mpCfg->mSmtGrp || smFailed)
		{
			// create subMeshes from materialID's groups
			Tab<int> mtGrps = pIMesh->GetActiveMatIDs();
			for (int x = 0; x < mtGrps.Count(); x++)
			{
				// update progress
				showProgress("MESH '" + pMesh->getName() + "' : SubMeshes/ByMaterial ", x, mtGrps.Count());
				// We need the face indices, so we cant do this
				// Tab<FaceEx*> mtFaces = pIMesh->GetFacesFromMatID(mtGrps[x]);
				Tab<FaceEx*> mtFaces;
				Tab<int> tiFaceIdx;
				// populate it
				mtFaces.Init();
				tiFaceIdx.Init();
				FaceEx *pxFace = NULL;
				int iFaceNum = 0;
				for (int iCnt = 0; iCnt < pIMesh->GetNumberOfFaces(); iCnt++)
				{
					pxFace = pIMesh->GetFace(iCnt);
					//Do we have the right material?
					if (pxFace->matID == mtGrps[x])
					{
						//okay==>add it to our Lists
						tiFaceIdx.Append(1, &iCnt, 10);
						//Store face index
						mtFaces.Append(1, &pxFace, 10);
					}
				}
				// get/create material
				Ogre::String matName = pMesh->getName() + "_SubMesh_" + Ogre::StringConverter::toString(x);
				IGameMaterial *pMat = pIMesh->GetMaterialFromFace(mtFaces[0]);
				if (pMat)
					matName = checkName(pMat->GetMaterialName());
				if (mpCfg->mMaterials)
					doMaterial(pIMesh->GetMaterialFromFace(mtFaces[0]));
				// create subMesh
				pSubMesh = pMesh->createSubMesh(matName);
				pSubMesh->setMaterialName(matName);
				pSubMesh->useSharedVertices = false;
				pSubMesh->vertexData = new Ogre::VertexData();
				pSubMesh->indexData = new Ogre::IndexData();
				size_t numVert = pSubMesh->vertexData->vertexCount = mtFaces.Count() * 3;
				pDecl = pSubMesh->vertexData->vertexDeclaration;
				pBind = pSubMesh->vertexData->vertexBufferBinding;
				//
				unsigned short iBinding = 0;
				// do positions first
				pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
				pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
				pBind->setBinding(iBinding, pBuf);
				pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
				for (int n = 0; n < mtFaces.Count(); n++)
				{
					FaceEx *pFace = mtFaces[n];
					for (int i = 0; i < 3; i++)
					{
						Point3 v; 
						if(pIMesh->GetVertex(pFace->vert[i], v))
						{
							*pReal++ = v.x;
							*pReal++ = v.y;
							*pReal++ = v.z;
							// Deal with bounds
							currPos = Ogre::Vector3(v.x, v.y, v.z);
							if (firstVert)
							{
								min = max = currPos;
								maxSquaredRadius = currPos.squaredLength();
								firstVert = false;
							}
							else
							{
								min.makeFloor(currPos);
								max.makeCeil(currPos);
								maxSquaredRadius = max(maxSquaredRadius, currPos.squaredLength());
							}
						}
					}// for vertex
				}// for faces
				// do normals
				if (mpCfg->mNormals && pIMesh->GetNumberOfNormals())
				{
					iBinding++;
					pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
					pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
					pBind->setBinding(iBinding, pBuf);
					pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
					for (int n = 0; n < mtFaces.Count(); n++)
					{
						FaceEx *pFace = mtFaces[n];
						for (int i = 0; i < 3; i++)
						{
							Point3 maxNor; 
							if(pIMesh->GetNormal(pFace->norm[i], maxNor))
							{
								Ogre::Vector3 nor;
								nor.x = maxNor.x;
								nor.y = maxNor.y;
								nor.z = maxNor.z;
								nor =  world2objN * nor;
								*pReal++ = nor.x;
								*pReal++ = nor.y;
								*pReal++ = nor.z;
							}
						}// for vertex
					}//for face
				}// normals
				// do tex.coords
				if (mpCfg->mTCoords)
				{
					// this will track the tex.coord.set index in our vertex buffer
					int numTC = 0;
					// do plain tex.coords first if there are any
					if (pIMesh->GetNumberOfTexVerts())
					{
						iBinding++;
						pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, numTC);
						pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
						pBind->setBinding(iBinding, pBuf);
						pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
						for (int t = 0; t < mtFaces.Count(); t++)
						{
							FaceEx *pFace = mtFaces[t];
							for (int ti = 0; ti < 3; ti++)
							{
								Point2 tc; 
								if(pIMesh->GetTexVertex(pFace->texCoord[ti], tc))
								{
									*pReal++ = tc.x;
									*pReal++ = (float)(1.0f - tc.y);
								}
							}// for vertex
						}//for face
					}//
					// now do mapping channels if the user requested it and tere are some defined
					Tab<int> xTab = pIMesh->GetActiveMapChannelNum();
					int iNumMapChannels = xTab.Count();
					if (iNumMapChannels && (mpCfg->mUV2 || mpCfg->mUVAll))
					{
						// strange, but it seems that the first mapping channel (ie the secondary)
						// is always at index 2, 1 is the standard tex.coord.buffer
						// the docs state that there is no access to the std.tcbuffer is channels are used :(
						// ..so...start from 2
						// anyway it's also weird that this tab use 1 based index (not 0 like the others :)
						for (int iMapChannel = 2; iMapChannel < iNumMapChannels + 2; iMapChannel++)
						{
							// first check if we'll get something
							DWORD aiFaceIndex[3];
							if (pIMesh->GetMapFaceIndex(iMapChannel, tiFaceIdx[0], (DWORD*)&aiFaceIndex))
							{
								iBinding++;
								numTC++;
								pDecl->addElement(iBinding, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, numTC);
								pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false );
								pBind->setBinding(iBinding, pBuf);
								pReal = static_cast<Ogre::Real*>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
								for (int face = 0; face < mtFaces.Count(); face++)
								{
									FaceEx *pFace = mtFaces[face];
									Point3 tc; 
									pIMesh->GetMapFaceIndex(iMapChannel, tiFaceIdx[face], (DWORD*)&aiFaceIndex);
									for (int vert = 0; vert < 3; vert++)
									{
										if(pIMesh->GetMapVertex(iMapChannel, aiFaceIndex[vert], tc))
										{
											*pReal++ = tc.x;
											*pReal++ = (float)(1.0f - tc.y);
										} 
										else
										{
											*pReal++ = 0;
											*pReal++ = 0;
										}
									}// for vertex
								}//for face
								// we got the first mapping channel so check if others are requested
								if (!mpCfg->mUVAll)
									break;
							}// if GetMapFaceIndex = true
						} //iMapChannel
					}// multi UV's
				}// text coords
				// do vertex colors
				if (mpCfg->mColors && pIMesh->GetNumberOfColorVerts())
				{
					iBinding++;
					pDecl->addElement(iBinding, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
					pBuf = mpBuffMgr->createVertexBuffer(pDecl->getVertexSize(iBinding), numVert, Ogre::HardwareBuffer::HBU_DYNAMIC, false);
					pBind->setBinding(iBinding, pBuf);
					unsigned long *pColor = static_cast<unsigned long *>(pBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
					for (int n = 0; n < mtFaces.Count(); n++)
					{
						FaceEx *pFace = mtFaces[n];
						for (int i = 0; i < 3; i++)
						{
							Point3 c; 
							if(pIMesh->GetColorVertex(pFace->color[i], c))
							{
								Ogre::ColourValue cv = Ogre::ColourValue(c.x, c.y, c.z, 1);
								*pColor++ = cv.getAsABGR();
							}
						}// for vertex
					}// for faces
				}// vertex colors
			}// for matID
		}// use matID's

		// do face indices
		for (int n = 0; n < pMesh->getNumSubMeshes(); ++n)
		{
			// update progress
			showProgress("MESH '" + pMesh->getName() + "' : Indices/SubMesh ", n, pMesh->getNumSubMeshes());

			pSubMesh = pMesh->getSubMesh(n);
			bool useWords = (pSubMesh->vertexData->vertexCount >= 65535) ? false : true;
			pSubMesh->indexData->indexCount = pSubMesh->vertexData->vertexCount;
			Ogre::HardwareIndexBufferSharedPtr pIBuf = 
				mpBuffMgr->createIndexBuffer(	useWords ? Ogre::HardwareIndexBuffer::IT_16BIT : Ogre::HardwareIndexBuffer::IT_32BIT, 
				pSubMesh->indexData->indexCount, 
				Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
			pSubMesh->indexData->indexBuffer = pIBuf;

			unsigned short *pWords = useWords ? static_cast<unsigned short*>(pIBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)) : NULL;
			unsigned int *pDWords = useWords ? NULL : static_cast<unsigned int*>(pIBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
			for (int i = 0; i < (int)pSubMesh->indexData->indexCount; i++)
			{
				if (useWords)
					*pWords++ = i;
				else
					*pDWords++ = i;
			}
		}

		// Set bounds
		pMesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(maxSquaredRadius));
		pMesh->_setBounds(Ogre::AxisAlignedBox(min, max));
		// do edge list generation if asked to
		if (mpCfg->mEdgeList)
		{
			try {pMesh->buildEdgeList();}
			catch (Ogre::Exception ex) {}
		}
		// do tangents generation if asked to
		if (mpCfg->mTangents)
		{
			try
			{
				unsigned short src = 0, dest = 0;
				if (pMesh->suggestTangentVectorBuildParams(src, dest))
					pMesh->buildTangentVectors(src, dest);
			}
			catch (Ogre::Exception ex)
			{
			}
		}

		// save mesh and finish ...
		mpMeshSer->exportMesh(pMesh.getPointer(), mFilePath + pMesh->getName() + ".mesh");
		sMeshInfo *pMI = new sMeshInfo();
		pMI->pMesh = pMesh;
		pMI->object.fromNode(pNode);
		mMeshes.push_back(pMI);
	}
	/***********************************************************************/
	void maxExporter::doGroupOwner(IGameNode *pNode)
	{
		sObjInfo *pObj = new sObjInfo();
		pObj->fromNode(pNode);
		mGrpOwners.push_back(pObj);
	}
	/***********************************************************************/
	void maxExporter::doTarget(IGameNode *pNode)
	{
		sObjInfo *pObj = new sObjInfo();
		pObj->fromNode(pNode);
		mTargets.push_back(pObj);
	}
	/***********************************************************************/
	void maxExporter::doLight(IGameNode *pNode)
	{
		IGameLight *pILight = (IGameLight *)pNode->GetIGameObject();
		if (!pILight->IsEntitySupported())
			return;

		if (!pILight->GetLightTarget() && pILight->GetLightType() != IGameLight::IGAME_OMNI)
			return;

		sLightInfo *pObj = new sLightInfo();
		// std object data
		pObj->object.fromNode(pNode);
		// light color
		if (pILight->GetLightColor())
		{
			Point3 color = readPoint3Prop(pILight->GetLightColor());
			pObj->color.r = color.x;
			pObj->color.g = color.y;
			pObj->color.b = color.z;
		}
		else
			pObj->color = Ogre::ColourValue::White;

		// target/type
		pObj->targetName = "";
		pObj->targetID = -1;
		switch (pILight->GetLightType())
		{
		case IGameLight::LightType::IGAME_DIR:
			pObj->type = "directional";
			break;
		case IGameLight::LightType::IGAME_FSPOT:
			pObj->type = "spot";
			break;
		case IGameLight::LightType::IGAME_OMNI:
			pObj->type = "point";
			break;
		case IGameLight::LightType::IGAME_TDIR:
			pObj->type = "directional";
			pObj->targetName = pILight->GetLightTarget() ? checkName(pILight->GetLightTarget()->GetName()) : "";
			pObj->targetID = pILight->GetLightTarget() ? pILight->GetLightTarget()->GetNodeID() : -1;
			break;
		case IGameLight::LightType::IGAME_TSPOT:
			pObj->type = "spot";
			pObj->targetName = pILight->GetLightTarget() ? checkName(pILight->GetLightTarget()->GetName()) : "";
			pObj->targetID = pILight->GetLightTarget() ? pILight->GetLightTarget()->GetNodeID() : -1;
			break;
		}
		// visibility
		pObj->visible = pILight->IsLightOn();
		// save data
		mLights.push_back(pObj);
	}
	/***********************************************************************/
	void maxExporter::doCamera(IGameNode *pNode)
	{
		IGameCamera *pCam = (IGameCamera *)pNode->GetIGameObject();
		if (!pCam->IsEntitySupported())
			return;

		if (!pCam->GetCameraTarget())
			return;

		IGameProperty *pProp = NULL;
		sCamInfo *pObj = new sCamInfo();

		pObj->object.fromNode(pNode);
		
		if (pCam->GetCameraFarClip())
			pObj->farClipDist = readFloatProp(pCam->GetCameraFarClip());
		else
			pObj->farClipDist = 0;
		if (pCam->GetCameraNearClip())
			pObj->nearClipDist = readFloatProp(pCam->GetCameraNearClip());
		else
			pObj->nearClipDist = 0;
		if (pCam->GetCameraFOV())
			pObj->fov = readFloatProp(pCam->GetCameraFOV());
		else
			pObj->fov = 0;

		pObj->targetName = pCam->GetCameraTarget() ? checkName(pCam->GetCameraTarget()->GetName()) : "";
		pObj->targetID = pCam->GetCameraTarget() ? pCam->GetCameraTarget()->GetNodeID() : -1;

		mCameras.push_back(pObj);
	}
	/***********************************************************************/
	void maxExporter::doBone(IGameNode *pNode)
	{
		IGameSupportObject *pIBone = (IGameSupportObject *)pNode->GetIGameObject();
		if (!pIBone->IsEntitySupported())
			return;

		sBoneInfo *pObj = new sBoneInfo();
		pObj->object.fromNode(pNode);
		mBones.push_back(pObj);
	}
	/***********************************************************************/
	void maxExporter::doGameObject(IGameNode *pNode)
	{
		IGameObject *pObject = pNode->GetIGameObject();
		IGameObject::ObjectTypes type = pObject->GetIGameType();
		switch(type)
		{
		case IGameObject::IGAME_LIGHT:
			if (mpCfg->mLights)
				doLight(pNode);
			break;
		case IGameObject::IGAME_MESH:
			if (mpCfg->mMeshes)
				doMesh(pNode);
			break;
		case IGameObject::IGAME_CAMERA:
			if (mpCfg->mCameras)
				doCamera(pNode);
			break;
		case IGameObject::IGAME_BONE:
			//doBone(pNode);
			break;
		case IGameObject::IGAME_SPLINE:
		case IGameObject::IGAME_HELPER:
		case IGameObject::IGAME_IKCHAIN:
		case IGameObject::IGAME_UNKNOWN:
			break;
		}
	}
	/***********************************************************************/
	void maxExporter::doNode(IGameNode *pNode)
	{
		if (pNode->IsGroupOwner())
			doGroupOwner(pNode);
		else if (pNode->IsTarget())
			doTarget(pNode);
		else
			doGameObject(pNode);

		// do childs
		for (int n = 0; n < pNode->GetChildCount(); n++)
			doNode(pNode->GetNodeChild(n));
	}
	/***********************************************************************/
	void maxExporter::doNodes()
	{
		// do top level nodes
		for(int loop = 0; loop < mpIGame->GetTopLevelNodeCount(); loop++)
			doNode(mpIGame->GetTopLevelNode(loop));
	}
	/***********************************************************************/
	void maxExporter::saveMaterials()
	{
		// do materials if asked ;)	
		mMatFileName = "";
		if (mpCfg->mMaterials && mpMatSer->getQueuedAsString().length() > 0)
		{
			utils::removeExtension(mFileName, mMatFileName);
			mMatFileName += ".material";
			mpMatSer->exportQueued(mMatFileName);
		}
	}
	/***********************************************************************/
	void maxExporter::export(Interface *pInterface, sPluginConfigOptions *pCfg, bool doSelected)
	{
		try
		{
			if (initIGame(doSelected))
			{
				mpCfg = new sPluginConfigOptions();
				memcpy(mpCfg, pCfg, sizeof(sPluginConfigOptions));
				mpMAXInterface = pInterface;

				if (setFileNames())
				{
					doNodes();
					saveMaterials();
					saveXML();
				}
			}
			else
			{
				MessageBox(NULL, "Nothing to export ??", "nonThink", MB_OK | MB_ICONERROR);
				return;
			}
			shutDownIGame();
		}
		catch (Ogre::Exception ex)
		{
			MessageBox(NULL, ex.getFullDescription().c_str(), "Erooorrrr", MB_OK | MB_ICONERROR);
			shutDownIGame();
		}
	}
	/***********************************************************************/
	void maxExporter::saveXML()
	{
		// write progress
		showProgress("Writing XML dotScene");

		TiXmlDocument *pXMLDoc = new TiXmlDocument();
		pXMLDoc->InsertEndChild(TiXmlElement("scene"));

		// export basic info
		TiXmlElement *rootNode = pXMLDoc->RootElement();
		rootNode->SetAttribute("id", "0");
		rootNode->SetAttribute("formatVersion", "0.2.0");
		rootNode->SetAttribute("sceneManager", "any");
		rootNode->SetAttribute("minOgreVersion", "1.0");
		rootNode->SetAttribute("author", "Ogre MAX .scene exporter plugin by cTh");

		// create the "environment" node
		TiXmlElement *envNode = rootNode->InsertEndChild(TiXmlElement("environment"))->ToElement();
		if (mpMAXInterface)
		{
			Point3 ac, bc;
			ac = mpMAXInterface->GetAmbient(TimeValue(0), Interval());
			bc = mpMAXInterface->GetBackGround(TimeValue(0), Interval());
			writeXmlColor(envNode, "colourAmbient", Ogre::ColourValue(ac.x, ac.y, ac.z));
			writeXmlColor(envNode, "colourBackground", Ogre::ColourValue(bc.x, bc.y, bc.z));
		}

		if (mMatFileName != "")
		{
			// create a externals entry for the material file reference
			TiXmlElement *extNode = rootNode->InsertEndChild(TiXmlElement("externals"))->ToElement();

			TiXmlElement *itemNode = extNode->InsertEndChild(TiXmlElement("item"))->ToElement();
			itemNode->SetAttribute("type", "material");
			TiXmlElement *fileNode = itemNode->InsertEndChild(TiXmlElement("file"))->ToElement();

			Ogre::String tmp;
			utils::extractFileName(mMatFileName, tmp);
			fileNode->SetAttribute("name", tmp.c_str());
		}

		// create the "Scene Root" node
		TiXmlElement *sceneRootNode = rootNode->InsertEndChild(TiXmlElement("nodes"))->ToElement();
		//sceneRootNode->SetAttribute("name", "Scene Root");
		//sceneRootNode->SetAttribute("id", "-1");

		//iterate through group owners first
		// we'll store all parent(grp. owners) there
		std::vector<TiXmlElement *> parents;
		for (std::vector<sObjInfo*>::iterator it = mGrpOwners.begin(); it != mGrpOwners.end(); ++it)
		{
			TiXmlElement *pThisNode = sceneRootNode->InsertEndChild(TiXmlElement("node"))->ToElement();
			writeXmlObject(pThisNode, (*it));
			parents.push_back(pThisNode);
		}

		//iterate through targets
		for (std::vector<sObjInfo*>::iterator it = mTargets.begin(); it != mTargets.end(); ++it)
		{
			TiXmlElement *pThisNode = sceneRootNode->InsertEndChild(TiXmlElement("node"))->ToElement();
			writeXmlObject(pThisNode, (*it));
			writeXmlPRS(pThisNode, (*it)->prs, false, false);
			pThisNode->SetAttribute("isTarget", "true");
		}

		//iterate through meshes
		//no need to export PRS, the exporter bake transforms in geometry data, do not seem right but ... :(
		for (std::vector<sMeshInfo*>::iterator it = mMeshes.begin(); it != mMeshes.end(); ++it)
		{
			TiXmlElement *pThisNode = NULL;
			if ((*it)->object.parentID > -1)
			{
				TiXmlElement *pParent = NULL;
				for (std::vector<TiXmlElement*>::iterator pit = parents.begin(); pit != parents.end(); ++pit)
				{
					if ((*pit)->Attribute("name") == (*it)->object.parentName)
					{
						pParent = (*pit);
						break;
					}
				}
				if (pParent)
					pThisNode = pParent->InsertEndChild(TiXmlElement("node"))->ToElement();
			}
			if (!pThisNode)
				pThisNode = sceneRootNode->InsertEndChild(TiXmlElement("node"))->ToElement();
			writeXmlObject(pThisNode, (*it)->object);
			// add the mesh
			TiXmlElement *entNode = pThisNode->InsertEndChild(TiXmlElement("entity"))->ToElement();
			writeXmlObject(entNode, (*it)->object);
			entNode->SetAttribute("meshFile", Ogre::String((*it)->pMesh->getName() + ".mesh").c_str());
			entNode->SetAttribute("static", ((*it)->pMesh->getSkeleton().getPointer()) ? Ogre::String("true").c_str() : Ogre::String("false").c_str());
		}

		//iterate through lights
		for (std::vector<sLightInfo*>::iterator it = mLights.begin(); it != mLights.end(); ++it)
		{
			TiXmlElement *pThisNode = sceneRootNode->InsertEndChild(TiXmlElement("node"))->ToElement();
			writeXmlObject(pThisNode, (*it)->object);
			bool doRot = true;
			if ((*it)->targetID > -1 || (*it)->type == "point")
				doRot = false;
			writeXmlPRS(pThisNode, (*it)->object.prs, doRot, false);
			// add the light
			TiXmlElement *ltNode = pThisNode->InsertEndChild(TiXmlElement("light"))->ToElement();
			writeXmlObject(ltNode, (*it)->object);
			writeXmlColor(ltNode, "colourDiffuse", (*it)->color);
			ltNode->SetAttribute("type", (*it)->type.c_str());
			ltNode->SetAttribute("visible", Ogre::StringConverter::toString((*it)->visible).c_str());
			if ((*it)->targetID > -1)
			{
				TiXmlElement *pTT = pThisNode->InsertEndChild(TiXmlElement("trackTarget"))->ToElement();
				pTT->SetAttribute("nodeName", (*it)->targetName.c_str());
			}
		}

		//iterate through cameras
		for (std::vector<sCamInfo*>::iterator it = mCameras.begin(); it != mCameras.end(); ++it)
		{
			TiXmlElement *pThisNode = sceneRootNode->InsertEndChild(TiXmlElement("node"))->ToElement();
			writeXmlObject(pThisNode, (*it)->object);
			writeXmlPRS(pThisNode, (*it)->object.prs, false, false);//(*it)->object.prs, (*it)->targetID > -1 ? false : true, false);
			// add the camera
			TiXmlElement *camNode = pThisNode->InsertEndChild(TiXmlElement("camera"))->ToElement();
			writeXmlObject(camNode, &(*it)->object);
			camNode->SetAttribute("projectionType", "perspective");
			// do clipping
			if ((*it)->nearClipDist || (*it)->farClipDist)
			{
				TiXmlElement *pNode = camNode->InsertEndChild(TiXmlElement("clipping"))->ToElement();
				pNode->SetAttribute("near", Ogre::StringConverter::toString((*it)->nearClipDist).c_str());
				pNode->SetAttribute("far", Ogre::StringConverter::toString((*it)->farClipDist).c_str());
			}
			// fov/aspectRatio
			if ((*it)->fov)
				camNode->SetAttribute("fov", Ogre::StringConverter::toString((*it)->fov).c_str());
			if ((*it)->targetID > -1)
			{
				TiXmlElement *pTT = pThisNode->InsertEndChild(TiXmlElement("trackTarget"))->ToElement();
				pTT->SetAttribute("nodeName", (*it)->targetName.c_str());
			}
			else
				writeXmlDirection(camNode, (*it)->object.prs);
		}

		// save the file and be happy
		pXMLDoc->SaveFile(mFileName.c_str());
		delete pXMLDoc;
	}
	/***********************************************************************/
}// namespace OgreMaxExport
/***********************************************************************/
